package sw6.admin.gui.logic;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;

import sw6.admin.internal.Definitions;

/**
 * This class parses a given settings.xml file and returns a Menu instance. This instances
 * serves as the root of a tree structure. The Menu instance can contain muliple instances
 * of Menu, hence the tree structure.
 * @author sw6b
 *
 */
public class SettingParser {

	/**
	 * Loads an XML file and returns a tree structure representing the content of the XML file
	 * @param path The path of the file from witch to read XML data
	 * @param packageName The package name of the application the settings file bellongs to.
	 * @return Menu is the root of a tree structure that is returned. The tree structure
	 * represents the data of the xml file
	 * @throws SAXException If the XML file layout is bad. Have you validated your XML file
	 * @throws IOException If something when wrong when trying to read from the XML file
	 */
	public static Menu loadXML(String path, String packageName, PackageManager pm) throws SAXException, IOException
	{
		InputStream inputStream = null;
		File inFile = new File(path);
		inputStream = new FileInputStream(inFile);

		return loadXML(inputStream, packageName, pm);
	}

	/**
	 * Loads an XML file and returns a tree structure representing the content of the XML file
	 * @param inputStream The stream from witch to read XML data
	 * @param packageName The package name of the application the settings file bellongs to.
	 * @return Menu is the root of a tree structure that is returned. The tree structure
	 * represents the data of the xml file
	 * @throws SAXException If the XML file layout is bad. Have you validated your XML file
	 * @throws IOException If something when wrong when trying to read from the XML file
	 */
	public static Menu loadXML(InputStream inputStream, String packageName, PackageManager pm) throws SAXException, IOException {
		// Setup the XML DOM parser
		DocumentBuilderFactory dbf 	= DocumentBuilderFactory.newInstance();
		DocumentBuilder docBuilder	= null;
		try {
			docBuilder = dbf.newDocumentBuilder();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}

		Document doc = docBuilder.parse(inputStream);

		Element start =  ((Element)((Element) doc.getElementsByTagName(Definitions.XML_ROOT).item(0)).getElementsByTagName(Definitions.XML_VISIBLE).item(0));
		String packageLabel;
		try {
			if (pm != null)
				packageLabel = pm.getApplicationInfo(packageName, 0).loadLabel(pm).toString();
			else
				packageLabel = "ROOT";
		} catch (NameNotFoundException e) {
			packageLabel = "ROOT";
		}
		Menu rootMenu = new Menu(packageLabel, "ROOT", packageName);

		if (start != null)
			handleMenu(rootMenu, start);

		return rootMenu;
	}

	/**
	 * Parses all settings and sub-menues in menu. 
	 * @param menu The menu to parse 
	 * @param menuElement An element pointing at the menu in the DOM object
	 */
	private static void handleMenu(Menu menu, Element menuElement)
	{
		NodeList nodes = menuElement.getChildNodes();

		ArrayList<Element> integers = new ArrayList<Element>();
		ArrayList<Element> strings = new ArrayList<Element>();
		ArrayList<Element> doubles = new ArrayList<Element>();
		ArrayList<Element> booleans = new ArrayList<Element>();
		ArrayList<Element> objects = new ArrayList<Element>();
		ArrayList<Element> stdobjects = new ArrayList<Element>();
		ArrayList<Element> menues = new ArrayList<Element>();
		ArrayList<Element> enums = new ArrayList<Element>();

		// Each integer, string, double, boolean, object, menu or enum node is added to its respective ArrayList
		for (int i = 0; i < nodes.getLength(); i++) {
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_INT)) {
				integers.add((Element) nodes.item(i));
			}
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_STRING)) {
				strings.add((Element) nodes.item(i));
			}
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_DOUBLE)) {
				doubles.add((Element) nodes.item(i));
			}
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_BOOLEAN)) {
				booleans.add((Element) nodes.item(i));
			}
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_OBJECT)) {
				objects.add((Element) nodes.item(i));
			}
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_STDOBJECT)) {
				stdobjects.add((Element) nodes.item(i));
			}
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_MENU)) {
				menues.add((Element) nodes.item(i));
			}
			if (nodes.item(i).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_ENUM)) {
				enums.add((Element) nodes.item(i));
			}

		}

		// All integers, strings, double, booleans, objects and enums are parsed, and their properties stored in an instance of Setting.
		// When the setting setting has been parsed, it is added to menu
		handleElements(integers, menu, Definitions.XML_SETTING_TYPE_INT);
		handleElements(strings, menu, Definitions.XML_SETTING_TYPE_STRING);
		handleElements(doubles, menu, Definitions.XML_SETTING_TYPE_DOUBLE);
		handleElements(booleans, menu, Definitions.XML_SETTING_TYPE_BOOLEAN);
		handleElements(objects, menu, Definitions.XML_SETTING_TYPE_OBJECT);
		handleElements(stdobjects, menu, Definitions.XML_SETTING_TYPE_STDOBJECT);
		handleElements(enums, menu, Definitions.XML_SETTING_TYPE_ENUM);

		// All sub menues of menu is parsed recursively
		for (int i = 0; i < menues.size(); i++) {
			Element newMenuElement = (Element) menues.get(i);
			String realName = newMenuElement.getAttribute(Definitions.XML_ATTRIBUTE_TITLE);
			String description = newMenuElement.getAttribute(Definitions.XML_ATTRIBUTE_DESCRIPTION);

			Menu newMenu = new Menu(realName, description, menu.getPackageName());
			menu.addMenu(newMenu);
			handleMenu(newMenu, newMenuElement);
		}
	}

	/**
	 * Parses attributes for the settings in nodeList. 
	 * @param nodeList A list containing the elements of a specific type (eg. integer, double, enum).
	 * @param menu The menu that the settings belong to 
	 * @param type The data type of the settings.
	 */
	private static void handleElements(ArrayList<Element> nodeList, Menu menu, String type) {

		for (int i = 0; i < nodeList.size(); i++) {
			Element e = (Element)nodeList.get(i);
			// Parses common atributes
			String varName = e.getAttribute(Definitions.XML_ATTRIBUTE_VAR_NAME);
			String realName = e.getAttribute(Definitions.XML_ATTRIBUTE_REAL_NAME);
			String description = e.getAttribute(Definitions.XML_ATTRIBUTE_DESCRIPTION);
			String classPath = e.getAttribute(Definitions.XML_ATTRIBUTE_CLASS_PATH);
			String activity = e.getAttribute(Definitions.XML_ATTRIBUTE_ACTIVITY);
			String pc = e.getAttribute(Definitions.XML_ATTRIBUTE_PC);
			String typeName = e.getAttribute(Definitions.XML_ATTRIBUTE_TYPE_NAME);

			// If no real name is defined, the varName is set as realName
			if (realName.equals(""))
				realName = varName;

			// Parses the min and max values
			double doubleMin = -Double.MAX_VALUE;
			double doubleMax = Double.MAX_VALUE;
			int intMin = Integer.MIN_VALUE;
			int intMax = Integer.MAX_VALUE;
			
			if (e.hasAttribute(Definitions.XML_ATTRIBUTE_MIN)) {
				if (type == Definitions.XML_SETTING_TYPE_INT)
					intMin = Integer.valueOf(e.getAttribute(Definitions.XML_ATTRIBUTE_MIN).trim()).intValue();
				if (type == Definitions.XML_SETTING_TYPE_DOUBLE)
					doubleMin = Double.valueOf(e.getAttribute(Definitions.XML_ATTRIBUTE_MIN).trim()).doubleValue();
			}
			if (e.hasAttribute(Definitions.XML_ATTRIBUTE_MAX)) {
				if (type == Definitions.XML_SETTING_TYPE_INT || type == Definitions.XML_SETTING_TYPE_STRING)
					intMax = Integer.valueOf(e.getAttribute(Definitions.XML_ATTRIBUTE_MAX).trim()).intValue();
				if (type == Definitions.XML_SETTING_TYPE_DOUBLE)
					doubleMax = Double.valueOf(e.getAttribute(Definitions.XML_ATTRIBUTE_MAX).trim()).doubleValue();
			}

			// Parses enum
			if (type.equals(Definitions.XML_SETTING_TYPE_ENUM)) {
				ArrayList<SettingEnumElement> enumElements = new ArrayList<SettingEnumElement>();
				NodeList nodes = e.getChildNodes();

				// Parses each enum element
				for (int j = 0; j < nodes.getLength(); j++)  {
					if (nodes.item(j).getNodeName().equalsIgnoreCase(Definitions.XML_SETTING_TYPE_ENUM_ELEMENT))
					{
						Element element = (Element)nodes.item(j);
						String enumRealName = element.getAttribute(Definitions.XML_ATTRIBUTE_REAL_NAME);
						int enumValue = Integer.parseInt(element.getAttribute(Definitions.XML_ATTRIBUTE_VALUE));
						enumElements.add(new SettingEnumElement(enumRealName, enumValue));
					}
				}

				Setting setting = new Setting(type, realName, varName, description, classPath, doubleMin, doubleMax, intMin, intMax, activity, pc, typeName);
				setting.setEnumElements(enumElements);
				menu.addSetting(setting);
				continue;
			}

			menu.addSetting(new Setting(type, realName, varName, description, classPath, doubleMin, doubleMax, intMin, intMax, activity, pc, typeName));
		}
	}
}
